package com.gome.ocean.gddl.matrix;

import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import com.gome.ocean.common.enums.DataSourceStatus;
import com.gome.ocean.dao.model.diamond.ConfigInfoBO;
import com.gome.ocean.dao.model.gddl.AppBO;
import com.gome.ocean.dao.model.gddl.AppDatasourceBO;
import com.gome.ocean.dao.model.gddl.AppRuleBO;
import com.gome.ocean.dao.model.gddl.AppTableBO;
import com.gome.ocean.dao.model.gddl.GroupBO;
import com.gome.ocean.dao.model.gddl.GroupRuleBO;
import com.gome.ocean.service.diamond.ConfigInfoService;
import com.gome.ocean.service.gddl.AppDatasourceService;
import com.gome.ocean.service.gddl.AppRuleService;
import com.gome.ocean.service.gddl.AppService;
import com.gome.ocean.service.gddl.AppTableService;
import com.gome.ocean.service.gddl.GroupRuleService;
import com.gome.ocean.service.gddl.GroupService;
import com.gome.ocean.service.scheduler.SchedulerServiceBean;
import com.gome.ocean.service.zookeeper.client.ZkClient;
import com.taobao.tddl.atom.common.TAtomConstants;
import com.taobao.tddl.atom.config.TAtomConfParser;
import com.taobao.tddl.atom.config.TAtomDsConfDO;
import com.taobao.tddl.config.ConfigDataHandlerFactory;
import com.taobao.tddl.config.ConfigDataListener;
import com.taobao.tddl.config.impl.ConfigDataHandlerCity;
import com.taobao.tddl.group.config.Weight;
import com.taobao.tddl.group.jdbc.TGroupDataSource;
import com.taobao.tddl.rule.RuleCompatibleHelper;
import com.taobao.tddl.rule.TableRule;
import com.taobao.tddl.rule.VirtualTableRoot;
import com.taobao.tddl.rule.utils.StringXmlApplicationContext;

public abstract class GddlAppRuleConfig implements InitializingBean, DisposableBean {

    protected static final Logger               LOG               = LoggerFactory.getLogger(GddlAppRuleConfig.class);
    public static final String               ROOT_BEAN_NAME    = "vtabroot";
    public static final ClassLoader                       outerClassLoader  = GddlAppRuleConfig.class.getClassLoader();
    // 是否兼容历史老的rule，主要是tdd5代码修改过类的全路径，针对tddl3之前的rule需要考虑做兼容处理
    protected boolean                           compatibleOldRule = true;

    protected volatile ConfigDataHandlerFactory configDataHandlerFactory;

    protected String                            unitName;
    @Autowired
    protected ConfigInfoService                 configInfoService;
    @Autowired
    protected AppService                        appService;
    @Autowired
    protected AppTableService                   appTableService;
    @Autowired
    protected AppRuleService                    appRuleService;
    @Autowired
    protected AppDatasourceService              appDatasourceService;
    @Autowired
    protected GroupService                      groupService;
    @Autowired
    protected GroupRuleService                  groupRuleService;
    @Autowired
    protected SchedulerServiceBean              schedulerServiceBean;
    @Autowired
    private ZkClient                            zkClient;

    /**
     * 处理分组信息
     * 
     * @param appName
     * @param configInfoBO
     */
    protected void processGroup(AppBO appBO, ConfigInfoBO configInfoBO) {
        String dbgroupsInfo = configInfoBO.getContent();
        if (StringUtils.isNotEmpty(dbgroupsInfo)) {
            String[] groupInfos = StringUtils.split(dbgroupsInfo, ",");
            for (String groupKey : groupInfos) {
                GroupBO groupBO = groupService.findGroupBOAppIdAndGroupName(appBO.getId(), groupKey);
                if (groupBO == null) groupBO = new GroupBO();
                groupBO.setAppId(appBO.getId());
                groupBO.setGroupName(groupKey);
                groupService.insertGroupBO(groupBO);

                String fullGroupKey = TGroupDataSource.getFullDbGroupKey(groupKey);
                ConfigInfoBO dsWeightInfo = configInfoService.findConfigInfoBOByDataId(fullGroupKey);
                processGroupRule(appBO, groupBO, dsWeightInfo.getDataId(), dsWeightInfo.getContent());
            }
        }
    }

    /**
     * 处理分组规则
     * 
     * @param groupBO
     * @param dsWeightInfo
     */
    private void processGroupRule(AppBO appBO, GroupBO groupBO, String dataId, String dsWeightContent) {
        String[] dsWeights = StringUtils.split(dsWeightContent, ",");
        for (String dsWeight : dsWeights) {
            String[] rwpqs = StringUtils.split(dsWeight, ":");
            String dbName = rwpqs[0];
            GroupRuleBO groupRuleBO = groupRuleService.findGroupRuleBOByIdAndName(groupBO.getId(), dbName);
            if (groupRuleBO == null) groupRuleBO = new GroupRuleBO();
            groupRuleBO.setGroupId(groupBO.getId());
            groupRuleBO.setGroupRuleName(dbName);
            Long dataSourceId = processAppRuleDataSource(appBO, dbName);
            groupRuleBO.setDatasourceId(dataSourceId);
            if (rwpqs[1].equals("b")) {
                groupRuleBO.setReadWeight(0);
                groupRuleBO.setReadPriority(0);
                groupRuleBO.setWritePriority(0);
                groupRuleBO.setWriteWeight(0);
                groupRuleBO.setIsBak(1);
            } else {
                Weight weight = new Weight(rwpqs[1]);
                groupRuleBO.setReadWeight(weight.w);
                groupRuleBO.setReadPriority(weight.p);
                groupRuleBO.setWritePriority(weight.q);
                groupRuleBO.setWriteWeight(weight.r);
                groupRuleBO.setIsBak(0);
            }
            groupRuleService.insertGroupRuleBO(groupRuleBO);
        }
        configDataHandlerFactory = ConfigDataHandlerCity.getFactory(appBO.getAppName(), unitName);
        configDataHandlerFactory.getConfigDataHandler(dataId, new GroupListener()); // 注册dataId监听
        LOG.info("process group content =" + dsWeightContent);
    }

    /**
     * 获取app应用
     * 
     * @param appName
     * @return
     */
    protected AppBO getAppBO(String appName) {
        AppBO appBO = appService.findAppBOByAppName(appName);
        if(appBO == null){
        	synchronized (appName) {
        		appBO = new AppBO();
        		appBO.setAppName(appName);
        		appBO.setInit(1);
        		appService.insertAppBO(appBO);
        		return appBO;
			}
        }
        return appBO;
    }

    /**
     * 处理app 包含分库分表配置
     * 
     * @param appName
     * @param ruleConfig
     */
    protected void processApp(AppBO appBO, ConfigInfoBO ruleConfig) {
        VirtualTableRoot virtualTableRoot = buildRuleByStr(ruleConfig.getContent());
        Map<String, TableRule> tableRules = virtualTableRoot.getTableRules();
        for (Entry<String, TableRule> tableRuleEntry : tableRules.entrySet()) {
            TableRule tableRule = tableRuleEntry.getValue();
            if (tableRule != null) {
                String virtualTbName = tableRule.getVirtualTbName().toLowerCase();
                AppTableBO appTableBO = appTableService.findAppTableByLogicTableName(virtualTbName);
                if(appTableBO == null) appTableBO = new AppTableBO();
                String shardColumn = tableRule.getShardColumns().get(0); // 分片字段
                appTableBO.setLogicTableName(virtualTbName); // 逻辑表明
                appTableBO.setTableField(shardColumn); // 分片字段
                appBO.setAppName(appBO.getAppName());// 应用名称
                if (tableRule.getDbRuleStrs() != null) {
                    String dbruleStr = tableRule.getDbRuleStrs()[0];
                    if (dbruleStr.contains("hashCode")) {
                        StringBuffer dbArith = new StringBuffer().append(shardColumn)
                            .append(".HASHCODE")
                            .append(" % ")
                            .append(StringUtils.substringAfter(dbruleStr, "%"));
                        appTableBO.setDbArith(dbArith.toString());// 分库规则
                    } else {
                        appTableBO.setDbArith(shardColumn.concat(" % ").concat(StringUtils.substringAfter(dbruleStr,
                            "%")));// 分库规则
                    }
                }
                if (tableRule.getTbRulesStrs() != null) {
                    String tbruleStr = tableRule.getTbRulesStrs()[0];
                    if (tbruleStr.contains("hashCode")) {
                        StringBuffer tbArith = new StringBuffer().append(shardColumn)
                            .append(".HASHCODE")
                            .append(" % ")
                            .append(StringUtils.substringAfter(tbruleStr, "%"));
                        appTableBO.setTbArith(tbArith.toString());// 分表规则
                    } else {
                        appTableBO.setTbArith(shardColumn.concat(" % ").concat(StringUtils.substringAfter(tbruleStr,
                            "%")));// 分表规则
                    }
                }
                int dbsize = tableRule.getActualTopology().size(); // 库数量
                int tbsize = 0; // 表数量
                for (Entry<String, Set<String>> tbEntry : tableRule.getActualTopology().entrySet()) {
                    tbsize += tbEntry.getValue().size();
                }
                appTableBO.setConfiguration(dbsize + "/" + (tbsize == 0 ? 1 : tbsize));// 分库分表配置
                appTableBO.setAppId(appBO.getId());
                appTableService.insertAppTableBO(appTableBO);

                processAppRule(appBO, appTableBO, tableRule); // 处理规则
            }
        }
        configDataHandlerFactory = ConfigDataHandlerCity.getFactory(appBO.getAppName(), unitName);
        configDataHandlerFactory.getConfigDataHandler(ruleConfig.getDataId(), new AppRuleListener()); // 注册dataId监听
        LOG.info("process app name=" + appBO.getAppName());
    }

    /**
     * 处理 app rule
     * 
     * @param appName
     * @param actualTopology
     */
    private void processAppRule(AppBO appBO, AppTableBO appTableBO, TableRule tableRule) {
        for (Entry<String, Set<String>> actualTopology : tableRule.getActualTopology().entrySet()) {
            for (String tableName : actualTopology.getValue()) {

                AppRuleBO appRuleBO = appRuleService.findAppRuleBOByAppIdAndExpression(appTableBO.getId(), tableName);

                if (appRuleBO == null) appRuleBO = new AppRuleBO();
                String routeRange = StringUtils.substringAfterLast(tableName, "_");
                if (StringUtils.isNotEmpty(routeRange) && StringUtils.isNumeric(routeRange)) {
                    appRuleBO.setRouteRange(Integer.valueOf(routeRange));
                } else {
                    appRuleBO.setRouteRange(0);
                }
                appRuleBO.setTableExpression(tableName.toLowerCase());
                appRuleBO.setAppTableId(appTableBO.getId());

                String groupKey = TGroupDataSource.getFullDbGroupKey(actualTopology.getKey());
                ConfigInfoBO groupInfo = configInfoService.findConfigInfoBOByDataId(groupKey);
                if (groupInfo != null) {
                    String[] dsWeights = StringUtils.split(groupInfo.getContent(), ",");
                    for (String dsWeight : dsWeights) {
                        String[] rwpqs = StringUtils.split(dsWeight, ":");
                        String dbName = rwpqs[0];
                        if (rwpqs[1].equals("b")) continue;
                        Long dataSrouceId = processAppRuleDataSource(appBO, dbName);
                        appRuleBO.setDatasourceId(dataSrouceId);
                        appRuleService.insertAppRuleBO(appRuleBO);
                    }
                }
            }
        }
    }

    /**
     * 解析数据源
     * 
     * @param appName
     * @param groupName
     * @return
     */
    private Long processAppRuleDataSource(AppBO appBO, String atomKey) {
        ConfigInfoBO globalInfo = configInfoService.findConfigInfoBOByDataId(TAtomConstants.getGlobalDataId(atomKey)); // global配置
        ConfigInfoBO appdataInfo = configInfoService.findConfigInfoBOByDataId(TAtomConstants.getAppDataId(appBO.getAppName(),
            atomKey)); // atom配置
        TAtomDsConfDO atomDsConfDO = TAtomConfParser.parserTAtomDsConfDO(globalInfo.getContent(),
            appdataInfo.getContent()); // 合并数据源
        // 获取密码
        ConfigInfoBO passwdInfo = configInfoService.findConfigInfoBOByDataId(TAtomConstants.getPasswdDataId(atomDsConfDO.getDbName(),
            atomDsConfDO.getDbType(),
            atomDsConfDO.getUserName()));
        // decode
        String passwd = null;
        try {
            passwd = TAtomConfParser.parserPasswd(passwdInfo.getContent());
            if(StringUtils.isEmpty(passwd)){
                Properties dbProperties = TAtomConfParser.parserConfStr2Properties(globalInfo.getContent());
                passwd = TAtomConfParser.parserPasswd(null, appBO.getAppName(), dbProperties.getProperty("dbName"), dbProperties.getProperty("ip"), dbProperties.getProperty("port"), passwdInfo.getDataId(), passwdInfo.getContent());
            }
        } catch (Exception e) {
            passwd = StringUtils.EMPTY;
        }
        atomDsConfDO.setPasswd(passwd);

        String jdbcUrl = atomDsConfDO.getIp().concat(":").concat(atomDsConfDO.getPort());
        AppDatasourceBO appDatasourceBO = appDatasourceService.findAppDatasourceBOByDbNameAndJdbcUrl(atomDsConfDO.getDbName(),
            jdbcUrl);

        if (appDatasourceBO == null) appDatasourceBO = new AppDatasourceBO();
        appDatasourceBO.setDbName(atomDsConfDO.getDbName());
        appDatasourceBO.setSourceType(atomDsConfDO.getDbType());
        appDatasourceBO.setUserName(atomDsConfDO.getUserName());
        appDatasourceBO.setPassword(atomDsConfDO.getPasswd());
        appDatasourceBO.setJdbcUrl(jdbcUrl);
        appDatasourceBO.setAppId(appBO.getId());
        appDatasourceBO.setSourceStatus(DataSourceStatus.SUCCESS.getValue());
        appDatasourceService.saveAppDatasourceBO(appDatasourceBO);
        return appDatasourceBO.getId();
    }

    /**
     * 编译 TDDL rule配置
     * 
     * @param version
     * @param data
     * @return
     */
    private VirtualTableRoot buildRuleByStr(String data) {
        if (compatibleOldRule) {
            data = RuleCompatibleHelper.compatibleRule(data);
        }
        @SuppressWarnings("resource")
        ApplicationContext applicationContext = new StringXmlApplicationContext(data, outerClassLoader);
        VirtualTableRoot tempvtr = (VirtualTableRoot) applicationContext.getBean(ROOT_BEAN_NAME);
        return tempvtr;
    }

    /**
     * 单个分组监听
     * 
     * @author haqiaolong 2016年3月28日 下午4:40:42
     * @since 5.0.0
     */
    class GroupListener implements ConfigDataListener {

        @Override
        public void onDataRecieved(final String dataId, final String data) {
            if (zkClient.isLeader()) {
                String groupName = StringUtils.substringAfter(dataId, TGroupDataSource.PREFIX);
                GroupBO groupBO = groupService.findGroupBOByGroupName(groupName);

                AppBO appBO = appService.findAppBOById(groupBO.getAppId());
                processGroupRule(appBO, groupBO, dataId, data);
                LOG.info("process group listener dataId =" + dataId);
            }
        }
    }

    /**
     * 规则监听
     * 
     * @author haqiaolong 2016年3月24日 下午6:39:08
     * @since 5.0.0
     */
    class AppRuleListener implements ConfigDataListener {

        @Override
        public void onDataRecieved(final String dataId, final String data) {
            if (zkClient.isLeader()) {
                String appName = StringUtils.substringBetween(dataId, "com.taobao.tddl.rule.le.", ".v_2.4.1");
                AppBO appBO = getAppBO(appName);
                if(appBO != null){
                	ConfigInfoBO ruleConfig = configInfoService.findConfigInfoBOByDataId(dataId);
                    processApp(appBO, ruleConfig);
                    LOG.info("process app listener dataId =" + dataId);
                }else{
                	LOG.info("ignore app listener dataId =" + dataId);
                }
                
            }
        }
    }

    /**
     * DB 分组监听
     * 
     * @author haqiaolong 2016年3月24日 下午6:39:20
     * @since 5.0.0
     */
    class DbGroupsListener implements ConfigDataListener {

        @Override
        public void onDataRecieved(final String dataId, final String data) {
            if (zkClient.isLeader()) {
                String appName = StringUtils.substringBetween(dataId, "com.taobao.tddl.v1_", "_dbgroups");
                AppBO appBO = getAppBO(appName);
                if(appBO != null){
                	ConfigInfoBO dbgroupsInfo = configInfoService.findConfigInfoBOByDataId(dataId);
                    processGroup(appBO, dbgroupsInfo);
                    LOG.info("process dbgroups listener dataId =" + dataId);
                }else{
                	LOG.info("ignore dbgroups listener dataId =" + dataId);
                }
            }
        }

    }

}
